using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using CSharpSynth.Effects;
using CSharpSynth.Sequencer;
using CSharpSynth.Synthesis;
using CSharpSynth.Midi;
using CSharpSynth.Banks.Analog;

//[RequireComponent (typeof(AudioSource))]
public class Synth : MonoBehaviour
{
	//midi octaves 3 - 7
	public const int MIN_NOTE = 36;
	public const int MAX_NOTE = 71 ;
	
	public const int MAX_VOLUME = 127;
	
	//Try also: "FM Bank/fm" or "Analog Bank/analog", "GM Bank/gm" for some different sounds
	private string bankFilePath = "GM Bank/gm";
	private int bufferSize = 1024;
	private int midiInstrument = 91;//there's only one in analog
	private float[] sampleBuffer;
	private float gain = .3f; //the max gain
	private StreamSynthesizer midiStreamSynthesizer;
	private int numChannels = 16;//number of channels in the streamSynthesizer -- I don't know if there's a maxstatic
	
	private Stack<int> freeChannels;//the available channels
	private HashSet<int> activeChannels;//the used channels
	
	void Awake ()
	{
		midiStreamSynthesizer = new StreamSynthesizer (44100, 2, bufferSize, 40);
		sampleBuffer = new float[midiStreamSynthesizer.BufferSize];		
		midiStreamSynthesizer.LoadBank (bankFilePath);
		
		freeChannels = new Stack<int>();
		activeChannels = new HashSet<int>();
		for (int i = 0; i < numChannels; i++) {
			freeChannels.Push(i);
		}
	}
	
	void Update() {
	}
	
	//the note, , note volume out of 127, percent of MAX_VOLUME to play it at
	public int Play(int note, float channelVolume, float noteVolumePercent, int instrument) {
		int channel = freeChannels.Pop();
		setChannelVolume (channel, channelVolume);//start the volume at 0 so it'll be set by the game
		midiStreamSynthesizer.NoteOn (channel, bindNote(note), calcVolume(noteVolumePercent), instrument);
		activeChannels.Add(channel);
		return channel;
	}
	
	public void Stop(int channel, int note) {
		if(activeChannels.Contains(channel)) {
			midiStreamSynthesizer.NoteOff (channel, note);
			activeChannels.Remove(channel);
			freeChannels.Push (channel);
		} else Debug.Log("Synth: Tried to stop a note on an inactive channel");
	}
	
	//set this channel's volume to vol % from 0 to 1
	public void setChannelVolume(int channel, float vol) {
		float newVolume;
		
		if (vol < 0) newVolume = 0;
		else if(vol > 1) newVolume = 1;
		else newVolume = vol;
		
		midiStreamSynthesizer.setVolume(channel, newVolume);
	}
	
	//set volume percent out of max_volume
	int calcVolume(float volPercent) {
		if (volPercent < 0) return 0;
		else if (volPercent > 1) return MAX_VOLUME;
		else return Mathf.RoundToInt(volPercent  * MAX_VOLUME);
	}
	
	//set gain, with constraints
	public void setPitchBend(int channel, float bend) {
		midiStreamSynthesizer.setPitchBend(channel, bend);
	}
	
	//bind a note to min/max range
	private int bindNote(int note) {
		if (note < MIN_NOTE) return MIN_NOTE;
		else if(note > MAX_NOTE) return MAX_NOTE;
		else return note;
	}
	
	// See http://unity3d.com/support/documentation/ScriptReference/MonoBehaviour.OnAudioFilterRead.html for reference code
	//	If OnAudioFilterRead is implemented, Unity will insert a custom filter into the audio DSP chain.
	//
	//	The filter is inserted in the same order as the MonoBehaviour script is shown in the inspector. 	
	//	OnAudioFilterRead is called everytime a chunk of audio is routed thru the filter (this happens frequently, every ~20ms depending on the samplerate and platform). 
	//	The audio data is an array of floats ranging from [-1.0f;1.0f] and contains audio from the previous filter in the chain or the AudioClip on the AudioSource. 
	//	If this is the first filter in the chain and a clip isn't attached to the audio source this filter will be 'played'. 
	//	That way you can use the filter as the audio clip, procedurally generating audio.
	//
	//	If OnAudioFilterRead is implemented a VU meter will show up in the inspector showing the outgoing samples level. 
	//	The process time of the filter is also measured and the spent milliseconds will show up next to the VU Meter 
	//	(it turns red if the filter is taking up too much time, so the mixer will starv audio data). 
	//	Also note, that OnAudioFilterRead is called on a different thread from the main thread (namely the audio thread) 
	//	so calling into many Unity functions from this function is not allowed ( a warning will show up ). 	
	private void OnAudioFilterRead (float[] data, int channels)
	{
		//This uses the Unity specific float method we added to get the buffer
		midiStreamSynthesizer.GetNext (sampleBuffer);
			
		for (int i = 0; i < data.Length; i++) {
			data [i] = sampleBuffer [i] * gain;
		}
	}
	
	/*
	public void MidiNoteOnHandler (int channel, int note, int velocity)
	{
		Debug.Log ("NoteOn: " + note.ToString () + " Velocity: " + velocity.ToString ());
	}
	
	public void MidiNoteOffHandler (int channel, int note)
	{
		Debug.Log ("NoteOff: " + note.ToString ());
	}*/
}
